﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web;
using SecureCookie.Implementation.Helper;
using SecureCookie.Implementation.Protocol;
using System.Threading;

namespace SecureCookie.Implementation.UnitTest
{
    /// <summary>
    /// Tests for <see cref="SecureCookieHttpModule"/>
    /// </summary>
    [TestClass]
    public class SecureCookieHttpModuleTests
    {
        /// <summary>
        /// Default is sliding disabled, this test should prove that when this setting is switched, a sliding interval is being
        /// granted.
        /// </summary>
        [TestMethod]
        public void Test_Scenario_Expiration_for_session_based_cookies_is_enabled()
        {
            // Set the configuration to have a max lifetime of 1 minute
            var configuration = new PrivateType(typeof(SecureCookieSettings));
            var settings = new SecureCookieSettings();
            settings["MaxLifetimeForSessionBasedCookiesIsSliding"] = true;
            configuration.SetStaticField("defaultInstance", settings);

            HttpCookie cookieFromFirstRequest = null;
            using (var firstRequest = new Stub.StubbedHttpContext())
            {
                firstRequest.ServerSideProcess(() =>
                {
                    firstRequest.ResponseCookies.Add(new HttpCookie("MyCookie1", "MyCookieValue1"));
                });
                cookieFromFirstRequest = firstRequest.ResponseCookies.Find("MyCookie1");
            }
            Thread.Sleep(5 * 100);
            HttpCookie cookieFromSecondRequest = null;
            using (var secondRequest = new Stub.StubbedHttpContext())
            {
                secondRequest.RequestCookies.Add(cookieFromFirstRequest);
                secondRequest.ServerSideProcess(() =>
                {
                    secondRequest.ResponseCookies.Add(new HttpCookie("MyCookie1", "MyCookieValue1"));
                });
                cookieFromSecondRequest = secondRequest.ResponseCookies.Find("MyCookie1");
            }
            Assert.IsNotNull(cookieFromFirstRequest, "cookie from first request should be set");
            Assert.IsNotNull(cookieFromSecondRequest, "cookie from second request should be set");
            Assert.IsTrue(cookieFromFirstRequest.GetExpirationPart().Value < cookieFromSecondRequest.GetExpirationPart().Value);
        }

        [TestMethod]
        public void Test_Scenario_Expiration_for_session_based_cookies_is_disabled()
        {
            // Set the configuration to have a max lifetime of 1 minute
            var configuration = new PrivateType(typeof(SecureCookieSettings));
            var settings = new SecureCookieSettings();
            settings["MaxLifetimeForSessionBasedCookiesIsSliding"] = false;
            configuration.SetStaticField("defaultInstance", settings);

            HttpCookie cookieFromFirstRequest = null;
            using (var firstRequest = new Stub.StubbedHttpContext())
            {
                firstRequest.ServerSideProcess(() =>
                {
                    firstRequest.ResponseCookies.Add(new HttpCookie("MyCookie1", "MyCookieValue1"));
                });
                cookieFromFirstRequest = firstRequest.ResponseCookies.Find("MyCookie1");
            }
            Thread.Sleep(5 * 100);
            HttpCookie cookieFromSecondRequest = null;
            using (var secondRequest = new Stub.StubbedHttpContext())
            {
                secondRequest.RequestCookies.Add(cookieFromFirstRequest);
                secondRequest.ServerSideProcess(() =>
                {
                    secondRequest.ResponseCookies.Add(new HttpCookie("MyCookie1", "MyCookieValue1"));
                });
                cookieFromSecondRequest = secondRequest.ResponseCookies.Find("MyCookie1");
            }
            Assert.IsNotNull(cookieFromFirstRequest, "cookie from first request should be set");
            Assert.IsNotNull(cookieFromSecondRequest, "cookie from second request should be set");
            Assert.AreEqual(cookieFromFirstRequest.GetExpirationPart().Value, cookieFromSecondRequest.GetExpirationPart().Value);
        }

        /// <summary>
        /// Test normal success flow
        /// </summary>
        [TestMethod]
        public void Test_Scenario_A_secured_cookie_that_is_resended_without_tampering_is_accepted()
        {
            HttpCookie securedCookie = null;

            using (var firstRequest = new Stub.StubbedHttpContext())
            {
                firstRequest.ServerSideProcess(() =>
                {
                    firstRequest.ResponseCookies.Add(new HttpCookie("MyCookie1", "MyCookieValue1"));
                });

                securedCookie = firstRequest.ResponseCookies.Find("MyCookie1");
            }
            Assert.IsNotNull(securedCookie, "Cookie should be on the response of the first request");

            using (var secondRequest = new Stub.StubbedHttpContext())
            {
                secondRequest.RequestCookies.Add(securedCookie);
                secondRequest.ServerSideProcess(() =>
                {
                    Assert.IsNotNull(secondRequest.RequestCookies.Find("MyCookie1")); // Should be on the request
                    Assert.AreEqual("MyCookieValue1", secondRequest.RequestCookies.Find("MyCookie1").Value); // Original value should be restored
                });
            }
        }
        /// <summary>
        /// Test failure flow
        /// </summary>
        [TestMethod]
        public void Test_Scenario_A_secured_cookie_that_is_resended_with_tampering_is_rejected()
        {
            HttpCookie securedCookie = null;

            using (var firstRequest = new Stub.StubbedHttpContext())
            {
                firstRequest.ServerSideProcess(() =>
                {
                    firstRequest.ResponseCookies.Add(new HttpCookie("MyCookie1", "MyCookieValue1"));
                });

                securedCookie = firstRequest.ResponseCookies.Find("MyCookie1");
            }
            Assert.IsNotNull(securedCookie, "Cookie should be on the response of the first request");
            securedCookie.Value = securedCookie.Value.Replace("MyCookieValue1", "MyCookieValueIsTamperted");

            using (var secondRequest = new Stub.StubbedHttpContext())
            {
                secondRequest.RequestCookies.Add(securedCookie);
                secondRequest.ServerSideProcess(() =>
                {
                    Assert.IsNull(secondRequest.RequestCookies.Find("MyCookie1")); // Should not be on the request
                });
            }
        }
        /// <summary>
        /// Test that only the configured cookies are being secured
        /// </summary>
        [TestMethod]
        public void Test_Scenario_All_configured_cookies_are_secured()
        {
            using (var context = new Stub.StubbedHttpContext())
            {
                context.RequestCookies.Add(new HttpCookie("TestCookie", "TestValue"));

                context.ServerSideProcess(() =>
                {
                    // user code or asp.net code set's some cookies:
                    context.ResponseCookies.Add(new HttpCookie("MyCookie1", "MyCookieValue1"));
                    context.ResponseCookies.Add(new HttpCookie("MyCookie2", "MyCookieValue2"));
                    context.ResponseCookies.Add(new HttpCookie("NotConfiguredCookie", "NotConfiguredCookieValue"));
                });

                Assert.AreEqual(3, context.ResponseCookies.Count);
                Assert.AreEqual("MyCookie1", context.ResponseCookies[1].Name);
                Assert.AreNotEqual("MyCookieValue1", context.ResponseCookies[1].Value);
                Assert.IsTrue(context.ResponseCookies[1].Value.Contains("MyCookieValue1"));

                Assert.AreEqual("MyCookie2", context.ResponseCookies[2].Name);
                Assert.AreNotEqual("MyCookieValue2", context.ResponseCookies[2].Value);
                Assert.IsTrue(context.ResponseCookies[2].Value.Contains("MyCookieValue2"));

                Assert.AreEqual("NotConfiguredCookieValue", context.ResponseCookies[0].Value);
            }
        }
    }
}
